﻿// Copyright (c) 2012, Alexander Endris
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#region Usings

using System;
using System.IO;
using NamelessOne.Imaging.BigTiff;
using NamelessOne.Imaging.Bmp;
using NamelessOne.Imaging.Jpg;
using NamelessOne.Imaging.Png;
using NamelessOne.Imaging.Tiff;

#endregion

namespace NamelessOne.Imaging
{
  public abstract class Image
  {
    #region Public / Attributes

    /// <summary>
    ///   Full path to the file
    /// </summary>
    public string FileName
    {
      get { return fileName; }
    }

    /// <summary>
    ///   Height of the image
    /// </summary>
    public int Height
    {
      get { return height; }
    }

    /// <summary>
    ///   Pixel format of the image
    /// </summary>
    public PixelFormat PixelFormat
    {
      get { return pixelFormat; }
    }

    /// <summary>
    ///   Width of the image
    /// </summary>
    public int Width
    {
      get { return width; }
    }

    #endregion

    #region Public / Methods

    /// <summary>
    ///   Loads the specified file as an image.
    /// </summary>
    /// <param name = "fileName">Full path to the file that is to be loaded</param>
    /// <returns>An Image of the image format that was recognized in the file.</returns>
    public static Image LoadFromFile(string fileName)
    {
      ImageFormat format = ReadImageFormat(fileName);

      switch (format)
      {
        case ImageFormat.Png:
          return new PngImage(fileName);
        case ImageFormat.Bmp:
          return new BmpImage(fileName);
        case ImageFormat.Tiff:
          return new TiffImage(fileName, false);
        case ImageFormat.TiffBigEndian:
          return new TiffImage(fileName, true);
        case ImageFormat.BigTiff:
          return new BigTiffImage(fileName, false);
        case ImageFormat.BigTiffBigEndian:
          return new BigTiffImage(fileName, true);
        case ImageFormat.Jpg:
          return new JpgImage(fileName);
        default:
          throw new ArgumentOutOfRangeException();
      }
    }

    /// <summary>
    ///   Führt anwendungsspezifische Aufgaben durch, die mit der Freigabe, der Zurückgabe oder dem Zurücksetzen von nicht verwalteten Ressourcen zusammenhängen.
    /// </summary>
    /// <filterpriority>2</filterpriority>
    public abstract void Dispose();

    /// <summary>
    ///   Gets the color values of the specified pixel
    /// </summary>
    /// <param name = "x">X-Coordinate of the pixel</param>
    /// <param name = "y">Y-Coordinate of the pixel</param>
    /// <returns>Color value of the specified pixel</returns>
    public abstract Color16 GetPixel(uint x, uint y);

    /// <summary>
    ///   Reads the next line of pixels and outputs it as a byte array.
    ///   The PixelFormat property has to be taken into account so the values in the byte array can be interpreted correcly
    /// </summary>
    /// <param name = "scanline">A byte array that contains the raw data of the pixels in the scanline. If there is no more scanline the byte array will have a length of zero</param>
    /// <returns>True if there was another scanline, false if there wasn't</returns>
    public abstract bool ReadNextScanline(out byte[] scanline);

    /// <summary>
    ///   Resets the internal scanline counter and streams so ReadNextScanline starts at the first scanline in the image.
    /// </summary>
    public abstract void ResetScanline();

    /// <summary>
    ///   Not yet implemented and supported
    /// </summary>
    /// <param name = "x"></param>
    /// <param name = "y"></param>
    /// <param name = "value"></param>
    public abstract void SetPixel(uint x, uint y, Color16 value);

    /// <summary>
    ///   Gibt einen <see cref = "T:System.String" /> zurück, der das aktuelle <see cref = "T:System.Object" /> darstellt.
    /// </summary>
    /// <returns>
    ///   Ein <see cref = "T:System.String" />, der das aktuelle <see cref = "T:System.Object" /> darstellt.
    /// </returns>
    /// <filterpriority>2</filterpriority>
    public override string ToString()
    {
      return string.Format("ImageFormat: {0}; PixelFormat: {1}; Width: {2}; Height: {3}", imageFormat, pixelFormat,
                           width, height);
    }

    #endregion

    #region Protected / Attributes

    protected int bytesPerPixel;
    protected string fileName;
    protected int height;
    protected PixelFormat pixelFormat;
    protected int width;

    #endregion

    #region Private / Attributes

    internal ImageFormat imageFormat;

    #endregion

    #region Private / Methods

    /// <summary>
    ///   Determines what image format the specified file is in
    /// </summary>
    /// <param name = "fileName">Full path to the file that is to be loaded</param>
    /// <returns>Image format of the specified file.</returns>
    private static ImageFormat ReadImageFormat(string fileName)
    {
      using (FileStream stream = new FileStream(fileName, FileMode.Open))
      using (BinaryReader reader = new BinaryReader(stream))
      {
        uint firstUInt = reader.ReadUInt32();

        // ‰PNG <-- PNG Identifier : 0x474E5089
        if (firstUInt == 0x474E5089)
          return ImageFormat.Png;
        // BM <-- BMP Identifier : 0x4D42
        if ((firstUInt & 0x0000FFFF) == 0x00004D42)
          return ImageFormat.Bmp;
        // II* <-- TIFF Identifier Little-Endian : 0x2A4949
        if ((firstUInt & 0x00FFFFFF) == 0x002A4949)
          return ImageFormat.Tiff;
        // MM* <-- TIFF Identifier Big-Endian : 0x2A4D4D
        if ((firstUInt & 0xFF00FFFF) == 0x2A004D4D)
          return ImageFormat.TiffBigEndian;
        // II+ <-- BigTIFF Identifier Little-Endian : 0x2B4949
        if ((firstUInt & 0x00FFFFFF) == 0x002B4949)
          return ImageFormat.BigTiff;
        // MM+ <-- BigTIFF Identifier Big-Endian : 0x2B4D4D
        if ((firstUInt & 0xFF00FFFF) == 0x2B004D4D)
          return ImageFormat.BigTiffBigEndian;
        // 0xE0FFD8FF <-- Start of Bitmap Marker + Application Marker of a JPG File
        if (firstUInt == 0xE0FFD8FF)
          return ImageFormat.Jpg;

        // No known image format
        return ImageFormat.Unknown;
      }
    }

    #endregion
  }
}